﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace Songhay.Wpf.Mvvm.ViewModels
{
    using Models;
    using Songhay.Models;
    using Songhay.Mvvm.Extensions;
    using Views;
    using Songhay.Mvvm.Models;

    /// <summary>
    /// Base class for all ViewModel classes in the application.
    /// It provides support for property change notifications 
    /// and has a DisplayName property. This class is abstract.
    /// </summary>
    /// <remarks>
    /// For details see
    /// “How to: Implement Property Change Notification”
    /// http://msdn.microsoft.com/en-us/library/ms743695.aspx
    /// 
    /// A sample <c>set</c> block:
    /// <code>
    /// set
    /// {
    ///     _myProperty = value;
    ///     OnPropertyChanged("MyProperty");
    /// }
    /// </code>
    /// </remarks>
    public abstract class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase, IDataErrorInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
        /// </summary>
        protected ViewModelBase()
        {
            this.SetDefaults();

            //Receive MvvmLight message:
            Messenger.Default.Register<GenericMessage<CommandDictionaryMessage>>(this,
                message =>
                {
                    this.CommandDictionary.Add(
                        message.Content.DictionaryKey,
                        message.Content.Command);
                });

            Messenger.Default.Register<GenericMessage<BackgroundWorkerCommandDictionaryMessage>>(this,
                message =>
                {
                    this.BackgroundWorkerCommandDictionary.Add(
                        message.Content.DictionaryKey,
                        message.Content.Command);
                });
        }

        #region MVVM Binding properties

        /// <summary>
        /// Gets the MVVM Light Client command.
        /// </summary>
        /// <value>The Client command.</value>
        public RelayCommand<string> ClientCommand
        {
            get
            {
                if(_commandInput == null)
                    _commandInput = new RelayCommand<string>(p =>
                    {
                        this.DoCommand(p, this.BackgroundWorkerCommandDictionary);
                        this.DoCommand(p, this.CommandDictionary);
                    });

                return _commandInput;
            }
        }

        /// <summary>
        /// Returns the user-friendly name of this object.
        /// </summary>
        public string DisplayName
        {
            get { return this._displayName; }
            set
            {
                this._displayName = value;
                this.RaisePropertyChanged("DisplayName");
            }
        }

        /// <summary>
        /// Returns the internal name of this object
        /// for retrieval from a collection.
        /// Child classes can set this property to a new value,
        /// or override it to determine the value on-demand.
        /// </summary>
        public string Name { get; set; }

        #endregion

        #region IDataErrorInfo Members

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get { return null; }
        }

        /// <summary>
        /// Gets the <see cref="System.String"/> with the specified column name.
        /// </summary>
        public string this[string propertyName]
        {
            get
            {
                if(this.ErrorMessageDictionary.Keys.Contains(propertyName))
                {
                    return this.ErrorMessageDictionary[propertyName];
                }

                var message = this.ValidateProperty(propertyName);
                if(!string.IsNullOrEmpty(message)) return message;

                return null;
            }
        }

        #endregion

        #region protected properties:

        /// <summary>
        /// Gets or sets the command dictionary.
        /// </summary>
        /// <value>The command dictionary.</value>
        protected Dictionary<string, BackgroundWorkerUtilityData> BackgroundWorkerCommandDictionary { get; set; }

        /// <summary>
        /// Gets or sets the command dictionary.
        /// </summary>
        /// <value>The command dictionary.</value>
        protected Dictionary<string, Action> CommandDictionary { get; set; }

        /// <summary>
        /// Gets or sets the error message dictionary.
        /// </summary>
        /// <value>The error message dictionary.</value>
        protected Dictionary<string, string> ErrorMessageDictionary { get; set; }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected bool ThrowOnInvalidPropertyName { get; set; }

        #endregion

        /// <summary>
        /// Sets the subclass for validation.
        /// </summary>
        /// <param name="subclassType">Type of the subclass.</param>
        protected void SetSubclassForValidation(Type subclassType)
        {
            this._subclassType = subclassType;
        }

        /// <summary>
        /// Validates the property.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <remarks>
        /// For details, see “WPF Binding : Use DataAnnotations for ValidationRules”
        /// [http://stackoverflow.com/questions/4903047/wpf-binding-use-dataannotations-for-validationrules]
        /// </remarks>
        protected string ValidateProperty(string propertyName)
        {
            var info = (this._subclassType == null) ?
                this.GetType().GetProperty(propertyName) :
                this._subclassType.GetProperty(propertyName);
            var value = info.GetValue(this, null);

            IEnumerable<string> errorInfos = info.GetCustomAttributes(true)
                .OfType<ValidationAttribute>()
                .Where(i => !i.IsValid(value))
                .Select(i => i.FormatErrorMessage(string.Empty));

            return (errorInfos.Count() > 0) ? errorInfos.FirstOrDefault<string>() : null;
        }

        void SetDefaults()
        {
            this.BackgroundWorkerCommandDictionary = new Dictionary<string, BackgroundWorkerUtilityData>();

            var commands = new Dictionary<string, Action>();
            commands.Add("#about", () =>
            {
                var about = new AboutView();
                about.Owner = Application.Current.MainWindow;
                about.ShowDialog();
            });
            commands.Add("#exit", () => Application.Current.Shutdown(0));
            this.CommandDictionary = commands;

            this.ErrorMessageDictionary = new Dictionary<string, string>();
        }

        RelayCommand<string> _commandInput;
        string _displayName;
        Type _subclassType;
    }
}
